/*
 *  Openmysee
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
				 
#include "echo.h"
extern time_t CurrentTime;
extern char *PREFIX;
static struct Channel *ProgramHash[MAX_CHANNEL];
static struct Channel *ProgramList;
static int NumNewProg;

static struct Channel *findProgram (char *name, int len)
{
	return getChannel (ProgramHash, name, len);
}
#define skipSeparator(ptr,end)		do\
{\
	while ((*ptr == '=' || isspace (*ptr)) && ptr < end) ptr ++;\
} while (0)
#define skipSpace(ptr,end)		do\
{\
	while (isspace (*ptr) && ptr < end) ptr ++;\
} while (0)

#define skipNonalnum(ptr,end)	do\
{\
	while ((!isalnum (*ptr)) && ptr < end) ptr ++;\
} while (0)

#define lineup(ptr,end)		do\
{\
	while (*ptr != '\r' && *ptr != '\n' && ptr < end) ptr ++;\
} while (0)

#define copyStr(src,dst,max,end)	do\
{\
	while ((!isspace (*ptr)) && max >= 0 && ptr < end)\
	{\
		*dst++ = *src++;\
		max --;\
	}\
	*dst = 0;\
} while (0)


static int read_prog_config (char *name, char *cname, int maxlen, float *bitrate, int *bsize, int *datalen, char **data)
{
	int len, size;
	FILE *in;
	char *buffer=NULL, *ptr, *end;
	char *p, *dst;
	struct stat stbuf;
	if (stat (name, &stbuf) != 0 || !(S_ISREG (stbuf.st_mode)))
	{
		PDEBUG ("Config file %s does not exist.\n", name);
		return -1;
	}
	if ((in=fopen (name, "r")) == NULL)
	{
		PDEBUG ("Cannot open file %s\n", name);
		return -1;
	}
	size = stbuf.st_size;
	buffer = calloc (1, size+1);
	if (fread (buffer, size, 1, in) != 1)
	{
		PDEBUG ("Cannot read all the file %s.\n", name);
		fclose (in);
		free (buffer);
		return -1;
	}
	end = buffer + size + 1;
	for (ptr=buffer; ptr<end;)
	{
		skipSpace (ptr, end);
		if (strncasecmp (ptr, "ChannelName", strlen ("ChannelName")) == 0)
		{
			ptr += strlen ("ChannelName");
			if (isalnum (*ptr))
			{
				lineup (ptr,end);
				continue;
			}
			skipSeparator(ptr,end);
			if (ptr >= end) break;
			copyStr (ptr, cname, maxlen,end);
			lineup (ptr,end);
		}
		else if (strncasecmp (ptr, "BitRate", strlen ("BitRate")) == 0)
		{
			ptr += strlen ("BitRate");
			if (isalnum (*ptr)) lineup (ptr,end);
			skipNonalnum (ptr,end);
			if (ptr >= end) break;
			*bitrate = atof (ptr);
			lineup (ptr,end);
		}
		else if (strncasecmp (ptr, "BlockSize", strlen ("BlockSize")) == 0)
		{
			ptr += strlen ("BlockSize");
			if (isalnum (*ptr)) lineup (ptr,end);
			skipNonalnum (ptr,end);
			if (ptr >= end) break;
			*bsize = atoi (ptr);
			lineup (ptr,end);
		}
		else if (strncasecmp (ptr, "DataLength", strlen ("DataLength")) == 0)
		{
			ptr += strlen ("DataLength");
			if (isalnum (*ptr)) lineup (ptr,end);
			skipNonalnum (ptr,end);
			if (ptr >= end) break;
			*datalen = atoi (ptr);
			lineup (ptr,end);
		}
		else if (strncasecmp (ptr, "Data", strlen ("Data")) == 0)
		{
			ptr += strlen ("Data=");
			len = *datalen;
			if (ptr >= end) break;
			dst = p = calloc (1, len+1);
			while (len >= 0)
			{
				*p++ = *ptr++;
				len --;
			}
			*data = dst;
			lineup (ptr,end);
		}
		else
		{
			lineup (ptr,end);
		}
	}
	fclose (in);
	if (buffer != NULL) free (buffer);
	return 0;
}

static inline void buildProgPath (char *buf, int len, char *md5)
{
	snprintf (buf, len, "%s/%s/", PREFIX, PROG_PREFIX);
	strcat (buf, md5);
}

// Return 1 to indicate write available, return 0 to indicate now writable.
static inline int newPListChannelFile (struct Channel *p)
{
	struct stat stbuf;
	char buffer[MAX_LINE];
	struct LiveChannelInfo *pcinfo = p->pcinfo;
	if (pcinfo->numinput >= MAX_FILEINPUT)
	{
		PDEBUG ("Max file input has been reached, %s:%d.\n", p->fname,pcinfo->numinput);
		return -1;
	}
	snprintf (buffer, MAX_LINE, "%s/%d", p->fname, pcinfo->numinput);
	if (stat (buffer, &stbuf) == 0 && (pcinfo->input[pcinfo->numinput] = fopen (buffer, "r")) != NULL)
	{
		pcinfo->maxID += stbuf.st_size / p->maxblocksize;
		pcinfo->numinput ++;
		return 0;
	}
	return -1;
}

static int init_prog (struct Channel *p)
{
	int i;
	struct stat stbuf;
	char buffer[MAX_LINE];
	struct LiveChannelInfo *pcinfo = p->pcinfo;
	if (p->maxblocksize == 0) p->maxblocksize = DEFAULT_BLOCK;
	while ((i = newPListChannelFile (p)) == 0);
	pcinfo->max_queue = BLOCK_PER_FILE;
	snprintf (buffer, MAX_LINE, "%s/keysample", p->fname);
	if (stat (buffer, &stbuf) == 0)
	{
		if (!S_ISREG (stbuf.st_mode))
		{
			PDEBUG ("File %s exist and not a regular file", buffer);
			return -1;
		}
	}
	pcinfo->keyfile = fopen (buffer, "r");
	if (pcinfo->keyfile == NULL)
	{
		PDEBUG ("File %s can not be opened.\n", buffer);
		return -1;
	}
	pcinfo->total = 0;
	pcinfo->isSave = 1;
//	timer_add (CurrentTime, (TimerFunc)send_spupdate, p, NULL);
	return 0;
}

static struct Channel *newProg (char *name, struct Session *source, char *cmd5, float bitrate, int maxblocksize)
{
	int id;
	struct Channel *p;
	struct LiveChannelInfo *pcinfo;
	if (NumNewProg >= MAX_CHANNEL) return (struct Channel *)0;
	p = (struct Channel *)calloc (sizeof (struct Channel), 1);
	memcpy (p->channel_md5, cmd5, MD5_LEN);
	if (name) strncpy (p->channel_name, name, sizeof (p->channel_name));
	p->channel_md5[MD5_LEN] = 0;
	p->upsize = 0;
	p->downsize = 0;
	p->maxblocksize = maxblocksize;
	p->ctime = time (NULL);

	p->pcinfo = (struct LiveChannelInfo *)calloc (sizeof (struct LiveChannelInfo), 1);
	pcinfo = p->pcinfo;
	pcinfo->dataSource = source;
	pcinfo->bitrate = bitrate;

	buildProgPath (p->fname, CHNLURL_LEN, cmd5);
	if (init_prog (p) < 0)
	{
		PDEBUG ("newPlistChannel error for %p.", p);
		free_livechannel (p);
		free (pcinfo);
		free (p);
		return (struct Channel *)0;
	}

	id = hash_str (p->channel_md5, MD5_LEN);
	PDEBUG("newPlistChannel hash %.32s(fname=%s) to %d.\n", p->channel_md5, p->fname, id);
	p->next = ProgramHash[id];
	ProgramHash[id] = p;
	p->lnext = ProgramList;
	ProgramList = p;
	NumNewProg ++;
	return p;
}

static struct Channel *add_prog (char *buffer, char *md5)
{
	struct stat stbuf;
	struct Channel *pc=NULL;
	char *data=NULL, cname[MAX_DATA];
	float bitrate=0.0;
	int bsize=16384, dlen=0;
	if (stat (buffer, &stbuf) < 0) return NULL;
	if (!S_ISDIR (stbuf.st_mode))
		return NULL;
	else
	{
		strcat (buffer, "/config");
		if (read_prog_config (buffer, cname, sizeof(cname), &bitrate, &bsize, &dlen, &data) < 0)
		{
			PDEBUG ("Error in parse file %s.\n", buffer);
			return NULL;
		}
		if ((pc=newProg (cname, NULL, md5, bitrate, bsize)) == NULL)
		{
			PDEBUG ("Cannot new plist Channel %s\n", cname);
			free (data);
			return NULL;
		}
		pc->pcinfo->media = calloc (1, sizeof (struct MediaData));
		pc->pcinfo->media[0].start = 0;
		pc->pcinfo->media[0].len = pc->pcinfo->maxID;
		pc->pcinfo->media[0].dlen = dlen;
		pc->pcinfo->media[0].data = data;
		pc->pcinfo->cur_channel = pc->pcinfo->max_channel = 1;
	}
	return pc;
}


struct Channel *getProgrambymd5 (char *name, int len)
{
	char buffer[MAX_DATA];
	struct Channel *result;
	if ((result = findProgram (name, len)) != NULL)
		return result;
	snprintf (buffer, MAX_DATA, "%s/%s/%s/", PREFIX, PROG_PREFIX, name);
	return add_prog (buffer, name);
}


int locateprog_by_id (struct Channel *pc, unsigned int id, char *buf, int max)
{
	int i, pos, *msg;
	struct LiveChannelInfo *c = pc->pcinfo;
	if (c == NULL)
	{
		PDEBUG ("c is %p and id is (%d,%d).\n", c, c->maxID, id);
		return -1;
	}
	i = id % c->maxID;
	pos = i / c->max_queue;
	i = i % c->max_queue;
	if (pc->maxblocksize + 2*sizeof(int) > max)
	{
		PDEBUG ("too small buffer %d for %d", max, pc->maxblocksize);
		return -2;
	}
	if (pos >= c->numinput || c->input[pos] == NULL)
	{
		PDEBUG ("file %d does not exist. (%d,%p)\n", pos, c->numinput, c->input[pos]);
		return -1;
	}

	if (fseeko (c->input[pos], ((off_t)(i)) * pc->maxblocksize, SEEK_SET) != 0)
	{
		PDEBUG ("Fssek failed. (%d, %d, %d)\n", pos, i, pc->maxblocksize);
		return -1;
	}
	if ((i=fread (buf+2*sizeof(int), 1, pc->maxblocksize, c->input[pos])) != pc->maxblocksize)
	{
		PDEBUG ("Fread failed. (%d, %d, %d)\n", pos, i, pc->maxblocksize);
		return -1;
	}
	msg = (int *)buf;
	msg[0] = id;
	msg[1] = pc->maxblocksize;
	pc->upsize += msg[1];
	return msg[1];
}

inline void freeProgram (struct Channel *pc, void *p)
{
	freeChannel (ProgramHash, &ProgramList, &NumNewProg, pc);
}

void freeAllProgram ()
{
	apply_hash (ProgramHash, freeProgram, NULL);
}
